package org.loong.crypto.service.software.provider.asymmetric.ec;

import java.security.InvalidAlgorithmParameterException;
import java.security.interfaces.ECPublicKey;
import java.security.spec.AlgorithmParameterSpec;
import java.util.Objects;
import java.util.Optional;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;

import org.apache.commons.lang3.ArrayUtils;
import org.bouncycastle.jce.spec.IESParameterSpec;
import org.loong.crypto.common.exception.CryptoException;
import org.loong.crypto.common.exception.InvalidKeyException;
import org.loong.crypto.core.algorithm.CipherAlgorithm;
import org.loong.crypto.core.params.CipherParameters;
import org.loong.crypto.core.utils.ECKeyUtils;
import org.loong.crypto.core.utils.SecureUtils;
import org.loong.crypto.service.core.provider.Encrypter;
import org.loong.crypto.service.core.provider.impl.ECIESCipherProvider;

public class ECIESEncrypter extends ECIESCipherProvider implements Encrypter {

    /**
     * The public key.
     */
    private ECPublicKey publicKey;

    /**
     * The ecies encrypt algorithm.
     */
    private String eciesAlgorithm;
    
    /**
     * The decrypt iv bytes.
     */
    private byte[] iv;
    
    /**
     * Creates a new encrypter.
     *
     * @param keyBytes the public key bytes. Must not be {@code null}.
     */
    public ECIESEncrypter(final byte[] keyBytes) {
        this(ECKeyUtils.toECPublicKey(keyBytes));
    }

    /**
     * Creates a new encrypter.
     *
     * @param publicKey the public key. Must not be {@code null}.
     */
    public ECIESEncrypter(final ECPublicKey publicKey) {
        if (publicKey == null) {
            throw new InvalidKeyException("The public EC key must not be null");
        }

        if (!"EC".equalsIgnoreCase(publicKey.getAlgorithm())) {
            throw new InvalidKeyException("The public key algorithm must be EC.");
        }

        this.publicKey = publicKey;
    }

    @Override
    public void init(CipherParameters parameters) {
        if (Objects.nonNull(parameters.getEncryptionMethod())) {
            this.eciesAlgorithm = parameters.getEncryptionMethod().getAlgorithm();
        }
        
        if (ArrayUtils.isNotEmpty(parameters.getIv())) {
            this.iv = parameters.getIv();
        }
    }
    
    @Override
    public byte[] encrypt(CipherAlgorithm algorithm, byte[] data) throws CryptoException {
        final String algorithmName = Optional.ofNullable(eciesAlgorithm).orElse(algorithm.getName());
        try {
            Cipher cipher = SecureUtils.createCipher(algorithmName);
            if (algorithmName.contains("CBC")) {
                AlgorithmParameterSpec params = new IESParameterSpec(null, null, 128, 128, iv);
                cipher.init(Cipher.ENCRYPT_MODE, publicKey, params);
            } else {
                cipher.init(Cipher.ENCRYPT_MODE, publicKey);
            }
            return cipher.doFinal(data);
        } catch (java.security.InvalidKeyException | IllegalBlockSizeException | BadPaddingException | InvalidAlgorithmParameterException e) {
            throw new CryptoException("ECIES encrypt exception: " + e.getMessage(), e);
        }
    }
}
